﻿using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;

namespace DeviceController.Wpf.SaveInfoServices
{
    public class TypeSaveInfoService : ISaveInfoService
    {
        private readonly ConcurrentDictionary<Type, string> _fileMap;

        private readonly ConcurrentDictionary<string, object> _lockMap;

        public TypeSaveInfoService()
        {
            _fileMap = new ConcurrentDictionary<Type, string>();
            _lockMap = new ConcurrentDictionary<string, object>();
        }

        private object GetLockObject(string fileName)
        {
            return _lockMap.GetOrAdd(fileName, new object());   
        }

        private string GetFileName<TModel>()
        {
            string? fileName;

            if (!_fileMap.TryGetValue(typeof(TModel), out fileName))
            {
                fileName = GetTypeMapFile(typeof(TModel));
                _fileMap[typeof(TModel)] = fileName;
            }

            return fileName;
        }

        public TModel Read<TModel>() where TModel : class, new()
        {
            var fileName = GetFileName<TModel>();

            lock (GetLockObject(fileName))
            {
                var stream = JsonFile.OpenStream(fileName);
                var model = JsonConfigFileHelper.Read<TModel>(stream);
                stream.Close();

                return model;
            }
        }

        public void Write<TModel>(TModel model) where TModel : class, new()
        {
            var fileName = GetFileName<TModel>();

            lock (GetLockObject(fileName))
            {
                var stream = JsonFile.OpenStream(fileName);
                JsonConfigFileHelper.Write(stream, model);
                stream.Close();
            }
        }

        /// <summary>
        /// 获取类型映射的文件名。
        /// </summary>
        private string GetTypeMapFile(Type type)
        {
            var attrs = type.GetCustomAttributes();
            var typeMapFile = attrs.OfType<TypeMapFileAttribute>().FirstOrDefault();
            var invalidPathChars = Path.GetInvalidPathChars();
            var invalidFileNameChars = Path.GetInvalidFileNameChars();
            var fileName = typeMapFile?.FileName ?? type.FullName;

            if (string.IsNullOrWhiteSpace(fileName))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射至文件名为空。");
            }

            // 将文件名拆开分别校验。
            var originDir = Path.GetDirectoryName(fileName);
            var originFileName = Path.GetFileName(fileName);

            if (originDir == null)
            {
                throw new InvalidOperationException("类型映射目录为 null。");
            }

            // 校验文件目录的合法性。
            if (originDir.Any(charValue => invalidPathChars.Contains(charValue)))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射文件名无效,映射文件名为{fileName}。");
            }

            // 校验文件名名称合法性。
            if (originFileName.Any(charValue => invalidFileNameChars.Contains(charValue)))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射文件名无效,映射文件名为{fileName}。");
            }

            var baseDir = typeMapFile?.BaseDirectory;

            // 校验基目录合法性。
            if (!string.IsNullOrWhiteSpace(baseDir)
                && baseDir.Any(charValue => invalidPathChars.Contains(charValue)))
            {
                throw new InvalidOperationException($"{type.FullName}类型映射基目录无效，映射基目录为{baseDir}。");
            }

            // 如果 .json 没有扩展名，追加 .json 扩展名。
            if (!string.Equals(Path.GetExtension(fileName), ".json", StringComparison.OrdinalIgnoreCase))
            {
                var strBuilder = new StringBuilder(fileName);
                strBuilder.Append(".json");
                fileName = strBuilder.ToString();
            }

            // 默认从 exe 目录开始引导。
            var exeDir = new FileInfo(Process.GetCurrentProcess()?.MainModule?.FileName ?? throw new InvalidOperationException("找不到程序模块文件。")).Directory?.FullName ?? throw new InvalidOperationException("文件模块目录名不存在。");

            // 注意：如果 baseDir 指定了盘符，那么 exeDir 将时无效的哟。
            fileName = Path.Combine(exeDir, baseDir ?? string.Empty, fileName);

            return fileName;
        }
    }
}
